using System;
using System.Collections.Generic;
using System.Text;

namespace Cocos2D
{
    public enum CCBPropType
    {
        Position = 0,
        Size,
        Point,
        PointLock,
        ScaleLock,
        Degrees,
        Integer,
        Float,
        FloatVar,
        Check,
        SpriteFrame,
        Texture,
        Byte,
        Color3,
        Color4FVar,
        Flip,
        Blendmode,
        FntFile,
        Text,
        FontTTF,
        IntegerLabeled,
        Block,
        Animation,
        CCBFile,
        String,
        BlockCCControl,
        FloatScale
    }

    internal enum CCBFloat
    {
        Float0 = 0,
        Float1,
        Minus1,
        Float05,
        Integer,
        Full
    }

    internal enum CCBPlatform
    {
        All = 0,
        IOS,
        Mac
    }

    public enum CCBTargetType
    {
        None = 0,
        DocumentRoot = 1,
        Owner = 2,
    }

    public enum CCBKeyframeEasing
    {
        Instant,

        Linear,

        CubicIn,
        CubicOut,
        CubicInOut,

        ElasticIn,
        ElasticOut,
        ElasticInOut,

        BounceIn,
        BounceOut,
        BounceInOut,

        BackIn,
        BackOut,
        BackInOut,
    }

    public enum CCBPositionType
    {
        RelativeBottomLeft,
        RelativeTopLeft,
        RelativeTopRight,
        RelativeBottomRight,
        Percent,
        MultiplyResolution,
    }

    internal enum CCBSizeType
    {
        Absolute,
        Percent,
        RelativeContainer,
        HorizontalPercent,
        VerticalPercent,
        MultiplyResolution,
    }

    public enum CCBScaleType
    {
        Absolute,
        MultiplyResolution
    }

    /**
     * @addtogroup cocosbuilder
     * @{
     */

    internal class CCBFile : CCNode
    {
        public CCNode FileNode { get; set; }
        public CCBFile() { }
    }


    public interface CCNodeLoaderListener
    {
        void OnNodeLoaded(CCNode node, CCNodeLoader nodeLoader);
    }

    public interface CCBMemberVariableAssigner
    {
        bool OnAssignCCBMemberVariable(object target, string memberVariableName, CCNode node);
    }

    public interface CCBSelectorResolver
    {
        SEL_MenuHandler OnResolveCCBCCMenuItemSelector(object target, string pSelectorName);
        SEL_CCControlHandler OnResolveCCBCCControlSelector(object target, string pSelectorName);
    }

    public interface CCBScriptOwnerProtocol
    {
        CCBSelectorResolver CreateNew();
    }

    /**
     * @brief Parse CCBI file which is generated by CocosBuilder
     */

    public class CCBReader 
    {
        private const int kCCBVersion = 3;

        private readonly List<string> mAnimatedProps = new List<string>();

        private readonly CCBMemberVariableAssigner mCCBMemberVariableAssigner;
        private readonly CCBSelectorResolver mCCBSelectorResolver;
        private readonly CCNodeLoaderLibrary mCCNodeLoaderLibrary;
        private readonly CCNodeLoaderListener mCCNodeLoaderListener;
        private readonly List<string> mLoadedSpriteSheets;
        private readonly List<string> mStringCache = new List<string>();

        public bool hasScriptingOwner = false;
        private CCBAnimationManager mActionManager;
        private byte[] mBytes;
        private int mCurrentBit;
        private int mCurrentByte;
        private object mOwner;

        public CCBReader(CCNodeLoaderLibrary nodeLoaderLibrary)
            : this(nodeLoaderLibrary, null, null, null)
        {
        }

        public CCBReader(CCNodeLoaderLibrary nodeLoaderLibrary, CCBMemberVariableAssigner memberVariableAssigner)
            : this(nodeLoaderLibrary, memberVariableAssigner, null, null)
        {
        }

        public CCBReader(CCNodeLoaderLibrary nodeLoaderLibrary, CCBMemberVariableAssigner memberVariableAssigner,
                         CCBSelectorResolver selectorResolver)
            : this(nodeLoaderLibrary, memberVariableAssigner, selectorResolver, null)
        {
        }

        public CCBReader(CCNodeLoaderLibrary nodeLoaderLibrary, CCBMemberVariableAssigner memberVariableAssigner,
                         CCBSelectorResolver selectorResolver, CCNodeLoaderListener nodeLoaderListener)
        {
            mCurrentByte = -1;
            mCurrentBit = -1;

            mLoadedSpriteSheets = new List<string>();

            mCCNodeLoaderLibrary = nodeLoaderLibrary;
            mCCBMemberVariableAssigner = memberVariableAssigner;
            mCCBSelectorResolver = selectorResolver;
            mCCNodeLoaderListener = nodeLoaderListener;
        }

        public CCBReader(CCBReader reader)
        {
            mCurrentByte = -1;
            mCurrentBit = -1;

            mLoadedSpriteSheets = reader.mLoadedSpriteSheets;
            mCCNodeLoaderLibrary = reader.mCCNodeLoaderLibrary;

            mCCBMemberVariableAssigner = reader.mCCBMemberVariableAssigner;
            mCCBSelectorResolver = reader.mCCBSelectorResolver;
            mCCNodeLoaderListener = reader.mCCNodeLoaderListener;
        }

        public CCBReader()
        {
            mCurrentByte = -1;
            mCurrentBit = -1;
        }

        public CCBMemberVariableAssigner MemberVariableAssigner
        {
            get { return mCCBMemberVariableAssigner; }
        }

        public CCBSelectorResolver SelectorResolver
        {
            get { return mCCBSelectorResolver; }
        }

        public CCBAnimationManager AnimationManager
        {
            get { return mActionManager; }
            set { mActionManager = value; }
        }

        // Used in CCNodeLoader.parseProperties()

        public List<string> AnimatedProperties
        {
            get { return mAnimatedProps; }
        }

        public List<string> LoadedSpriteSheet
        {
            get { return mLoadedSpriteSheets; }
        }

        public object Owner
        {
            get { return mOwner; }
        }

        public static float ResolutionScale
        {
            get
            {
                // Init resolution scale
                //if (CCApplication.SharedApplication.TargetPlatform == kTarget.kTargetIpad)
                //{
                //    return 2;
                //}
                //else 
                {
                    return 1;
                }
            }
        }

        public bool InitWithData(byte[] bytes, object owner)
        {
            // Setup action manager
            var pActionManager = new CCBAnimationManager();
            AnimationManager = pActionManager;

            // Setup byte array
            mBytes = bytes;
            mCurrentByte = 0;
            mCurrentBit = 0;

            mOwner = owner;

            // Setup resolution scale and container size
            mActionManager.RootContainerSize = CCDirector.SharedDirector.WinSize;

            return true;
        }

        public CCNode ReadNodeGraphFromFile(string fileName)
        {
            return ReadNodeGraphFromFile(fileName, null);
        }

        public CCNode ReadNodeGraphFromFile(string fileName, object owner)
        {
            return ReadNodeGraphFromFile(fileName, owner, CCDirector.SharedDirector.WinSize);
        }

        public CCNode ReadNodeGraphFromFile(string fileName, object owner, CCSize parentSize)
        {
            CCBAnimationManager dummy = null;
            return ReadNodeGraphFromFile(fileName, owner, parentSize, ref dummy);
        }

        public CCNode ReadNodeGraphFromFile(string fileName, object owner, ref CCBAnimationManager animationManager)
        {
            return ReadNodeGraphFromFile(fileName, owner, CCDirector.SharedDirector.WinSize, ref animationManager);
        }

        public CCNode ReadNodeGraphFromFile(string fileName, object owner, CCSize parentSize, ref CCBAnimationManager animationManager)
        {
            string pPath = CCFileUtils.FullPathFromRelativePath(fileName);
            byte[] pBytes = CCFileUtils.GetFileBytes(pPath);
            CCNode ret = ReadNodeGraphFromData(pBytes, owner, parentSize, ref animationManager);
            return ret;
        }

        public CCNode ReadNodeGraphFromData(byte[] bytes, object owner, CCSize parentSize, ref CCBAnimationManager animationManager)
        {
            InitWithData(bytes, owner);
            mActionManager.RootContainerSize = parentSize;

            CCNode pNodeGraph = ReadFileWithCleanUp(true);

            if (pNodeGraph != null && mActionManager.AutoPlaySequenceId != -1)
            {
                // Auto play animations
                mActionManager.RunAnimations(mActionManager.AutoPlaySequenceId, 0);
            }

            // Return action manager by reference
            animationManager = mActionManager;

            return pNodeGraph;
        }

        public CCNode ReadNodeGraphFromData(byte[] bytes, object owner, CCSize parentSize)
        {
            CCBAnimationManager dummy = null;
            return ReadNodeGraphFromData(bytes, owner, parentSize, ref dummy);
        }

        public CCScene CreateSceneWithNodeGraphFromFile(string fileName)
        {
            return CreateSceneWithNodeGraphFromFile(fileName, null);
        }

        public CCScene CreateSceneWithNodeGraphFromFile(string fileName, object owner)
        {
            return CreateSceneWithNodeGraphFromFile(fileName, owner, CCDirector.SharedDirector.WinSize);
        }

        public CCScene CreateSceneWithNodeGraphFromFile(string fileName, object owner, CCSize parentSize)
        {
            CCBAnimationManager dummy = null;
            return CreateSceneWithNodeGraphFromFile(fileName, owner, parentSize, ref dummy);
        }

        /*
        public CCScene createSceneWithNodeGraphFromFile(string pCCBFileName, object owner, ref CCBAnimationManager animationManager)
        {
            
        }
        */

        public CCScene CreateSceneWithNodeGraphFromFile(string fileName, object owner, CCSize parentSize,
                                                        ref CCBAnimationManager animationManager)
        {
            CCNode node = ReadNodeGraphFromFile(fileName, owner, parentSize, ref animationManager);
            CCScene pScene = new CCScene();
            pScene.AddChild(node);

            return pScene;
        }

        /* Utility methods. */

        public static String LastPathComponent(String pPath)
        {
            int slashPos = pPath.IndexOf('/');
            if (slashPos != -1)
            {
                return pPath.Substring(slashPos + 1);
            }
            return pPath;
        }

        public static String DeletePathExtension(String pPath)
        {
            int dotPos = pPath.LastIndexOf('.');
            if (dotPos != -1)
            {
                return pPath.Substring(0, dotPos);
            }
            return pPath;
        }

        public static String ToLowerCase(String pString)
        {
            return pString.ToLower();
        }

        public static bool EndsWith(String pString, String pEnding)
        {
            return pString.EndsWith(pEnding);
        }

        public static String Concat(String pStringA, String pStringB)
        {
            return pStringA + pStringB;
        }

        /* Parse methods. */

        public int ReadInt(bool pSigned)
        {
            int numBits = 0;
            while (!GetBit())
            {
                numBits++;
            }

            int current = 0;
            for (int a = numBits - 1; a >= 0; a--)
            {
                if (GetBit())
                {
                    current |= 1 << a;
                }
            }
            current |= 1 << numBits;

            int num;
            if (pSigned)
            {
                int s = current % 2;
                if (s != 0)
                {
                    num = (current / 2);
                }
                else
                {
                    num = (-current / 2);
                }
            }
            else
            {
                num = current - 1;
            }

            AlignBits();

            return num;
        }

        public byte ReadByte()
        {
            byte b = mBytes[mCurrentByte];
            mCurrentByte++;
            return b;
        }

        public bool ReadBool()
        {
            return 0 != ReadByte();
        }

        public float ReadFloat()
        {
            var type = (CCBFloat) ReadByte();

            switch (type)
            {
                case CCBFloat.Float0:
                    return 0;
                case CCBFloat.Float1:
                    return 1;
                case CCBFloat.Minus1:
                    return -1;
                case CCBFloat.Float05:
                    return 0.5f;
                case CCBFloat.Integer:
                    return ReadInt(true);
                default:
                    var byteArray = new byte[4];

                    byteArray[0] = mBytes[mCurrentByte + 0];
                    byteArray[1] = mBytes[mCurrentByte + 1];
                    byteArray[2] = mBytes[mCurrentByte + 2];
                    byteArray[3] = mBytes[mCurrentByte + 3];

                    float f = BitConverter.ToSingle(byteArray, 0);
                    mCurrentByte += 4;
                    return f;
            }
        }

        public string ReadCachedString()
        {
            int i = ReadInt(false);
            return mStringCache[i];
        }

        public CCNode ReadFileWithCleanUp(bool bCleanUp)
        {
            if (!ReadHeader())
            {
                return null;
            }

            if (!ReadStringCache())
            {
                return null;
            }

            if (!ReadSequences())
            {
                return null;
            }

            CCNode node = ReadNodeGraph();

            if (bCleanUp)
            {
                CleanUpNodeGraph(node);
            }

            return node;
        }

        private void CleanUpNodeGraph(CCNode node)
        {
            node.UserObject = null;

            if (node.Children != null)
            {
                for (int i = 0; i < node.Children.Count; i++)
                {
                    CleanUpNodeGraph(node.Children[i]);
                }
            }
        }

        private bool ReadSequences()
        {
            List<CCBSequence> sequences = mActionManager.Sequences;

            int numSeqs = ReadInt(false);

            for (int i = 0; i < numSeqs; i++)
            {
                var seq = new CCBSequence();

                seq.Duration = ReadFloat();
                seq.Name = ReadCachedString();
                seq.SequenceId = ReadInt(false);
                seq.ChainedSequenceId = ReadInt(true);

                sequences.Add(seq);
            }

            mActionManager.AutoPlaySequenceId = ReadInt(true);
            return true;
        }


        private CCBKeyframe ReadKeyframe(CCBPropType type)
        {
            var keyframe = new CCBKeyframe();

            keyframe.Time = ReadFloat();

            var easingType = (CCBKeyframeEasing) ReadInt(false);
            float easingOpt = 0;
            object value = null;

            if (easingType == CCBKeyframeEasing.CubicIn
                || easingType == CCBKeyframeEasing.CubicOut
                || easingType == CCBKeyframeEasing.CubicInOut
                || easingType == CCBKeyframeEasing.ElasticIn
                || easingType == CCBKeyframeEasing.ElasticOut
                || easingType == CCBKeyframeEasing.ElasticInOut)
            {
                easingOpt = ReadFloat();
            }
            keyframe.EasingType = easingType;
            keyframe.EasingOpt = easingOpt;

            if (type == CCBPropType.Check)
            {
                value = new CCBValue(ReadBool());
            }
            else if (type == CCBPropType.Byte)
            {
                value = new CCBValue(ReadByte());
            }
            else if (type == CCBPropType.Color3)
            {
                byte r = ReadByte();
                byte g = ReadByte();
                byte b = ReadByte();

                var c = new CCColor3B(r, g, b);
                value = new ccColor3BWapper(c);
            }
            else if (type == CCBPropType.Degrees)
            {
                value = new CCBValue(ReadFloat());
            }
            else if (type == CCBPropType.ScaleLock || type == CCBPropType.Position)
            {
                float a = ReadFloat();
                float b = ReadFloat();

                value = new List<CCBValue>
                    {
                        new CCBValue(a),
                        new CCBValue(b)
                    };
            }
            else if (type == CCBPropType.SpriteFrame)
            {
                string spriteSheet = ReadCachedString();
                string spriteFile = ReadCachedString();

                CCSpriteFrame spriteFrame;

                if (String.IsNullOrEmpty(spriteSheet))
                {
                    CCTexture2D texture = CCTextureCache.SharedTextureCache.AddImage(CCFileUtils.RemoveExtension(spriteFile));
                    var bounds = new CCRect(0, 0, texture.ContentSize.Width, texture.ContentSize.Height);
                    spriteFrame = new CCSpriteFrame(texture, bounds);
                }
                else
                {
                    CCSpriteFrameCache frameCache = CCSpriteFrameCache.SharedSpriteFrameCache;

                    // Load the sprite sheet only if it is not loaded            
                    if (!mLoadedSpriteSheets.Contains(spriteSheet))
                    {
                        frameCache.AddSpriteFramesWithFile(spriteSheet);
                        mLoadedSpriteSheets.Add(spriteSheet);
                    }

                    spriteFrame = frameCache.SpriteFrameByName(spriteFile);
                }
                value = spriteFrame;
            }

            keyframe.Value = value;

            return keyframe;
        }

        private bool ReadHeader()
        {
            /* If no bytes loaded, don't crash about it. */
            if (mBytes == null)
            {
                return false;
            }

            /* Read magic bytes */
            if (mBytes[mCurrentByte + 0] != 'i' || mBytes[mCurrentByte + 1] != 'b' || mBytes[mCurrentByte + 2] != 'c' ||
                mBytes[mCurrentByte + 3] != 'c')
            {
                return false;
            }

            mCurrentByte += 4;

            /* Read version. */
            int version = ReadInt(false);
            if (version != kCCBVersion)
            {
                CCLog.Log("WARNING! Incompatible ccbi file version (file: %d reader: %d)", version, kCCBVersion);
                return false;
            }

            return true;
        }

        private bool ReadStringCache()
        {
            int numStrings = ReadInt(false);

            for (int i = 0; i < numStrings; i++)
            {
                ReadStringCacheEntry();
            }

            return true;
        }

        private void ReadStringCacheEntry()
        {
            int b0 = ReadByte();
            int b1 = ReadByte();

            int numBytes = b0 << 8 | b1;


            string s = Encoding.UTF8.GetString(mBytes, mCurrentByte, numBytes);

            mCurrentByte += numBytes;

            mStringCache.Add(s);
        }

        private CCNode ReadNodeGraph()
        {
            return ReadNodeGraph(null);
        }

        private CCNode ReadNodeGraph(CCNode parent)
        {
            /* Read class name. */
            string className = ReadCachedString();

            // Read assignment type and name
            var memberVarAssignmentType = (CCBTargetType) ReadInt(false);

            string memberVarAssignmentName = String.Empty;
            if (memberVarAssignmentType != CCBTargetType.None)
            {
                memberVarAssignmentName = ReadCachedString();
            }

            CCNodeLoader ccNodeLoader = mCCNodeLoaderLibrary.GetCCNodeLoader(className);
            if (ccNodeLoader == null)
            {
                CCLog.Log("no corresponding node loader for %s", className);
                return null;
            }

            CCNode node = ccNodeLoader.LoadCCNode(parent, this);

            // Set root node
            if (mActionManager.RootNode == null)
            {
                mActionManager.RootNode = node;
            }

            // Read animated properties
            var seqs = new Dictionary<int, Dictionary<string, CCBSequenceProperty>>();
            mAnimatedProps.Clear();

            int numSequence = ReadInt(false);
            for (int i = 0; i < numSequence; ++i)
            {
                int seqId = ReadInt(false);
                var seqNodeProps = new Dictionary<string, CCBSequenceProperty>();

                int numProps = ReadInt(false);

                for (int j = 0; j < numProps; ++j)
                {
                    var seqProp = new CCBSequenceProperty();

                    seqProp.Name = ReadCachedString();
                    seqProp.Type = (CCBPropType) ReadInt(false);
                    mAnimatedProps.Add(seqProp.Name);

                    int numKeyframes = ReadInt(false);

                    for (int k = 0; k < numKeyframes; ++k)
                    {
                        CCBKeyframe keyframe = ReadKeyframe(seqProp.Type);

                        seqProp.Keyframes.Add(keyframe);
                    }

                    seqNodeProps.Add(seqProp.Name, seqProp);
                }

                seqs.Add(seqId, seqNodeProps);
            }

            if (seqs.Count > 0)
            {
                mActionManager.AddNode(node, seqs);
            }

            // Read properties
            ccNodeLoader.ParseProperties(node, parent, this);

            // Handle sub ccb files (remove middle node)
            if (node is CCBFile)
            {
                var ccbFileNode = (CCBFile) node;

                CCNode embeddedNode = ccbFileNode.FileNode;
                embeddedNode.Position = ccbFileNode.Position;
                embeddedNode.Rotation = ccbFileNode.Rotation;
                embeddedNode.Scale = ccbFileNode.Scale;
                embeddedNode.Tag = ccbFileNode.Tag;
                embeddedNode.Visible = true;
                embeddedNode.IgnoreAnchorPointForPosition = ccbFileNode.IgnoreAnchorPointForPosition;

                ccbFileNode.FileNode = null;

                node = embeddedNode;
            }

#if CCB_ENABLE_JAVASCRIPT
    /*
     if (memberVarAssignmentType && memberVarAssignmentName && ![memberVarAssignmentName isEqualToString:@""])
     {
     [[JSCocoa sharedController] setObject:node withName:memberVarAssignmentName];
     }*/
#else
            if (memberVarAssignmentType != CCBTargetType.None)
            {
                object target = null;
                if (memberVarAssignmentType == CCBTargetType.DocumentRoot)
                {
                    target = mActionManager.RootNode;
                }
                else if (memberVarAssignmentType == CCBTargetType.Owner)
                {
                    target = mOwner;
                }

                if (target != null)
                {
                    bool assigned = false;

                    var targetAsCCBMemberVariableAssigner = (CCBMemberVariableAssigner) target;

                    if (targetAsCCBMemberVariableAssigner != null)
                    {
                        assigned = targetAsCCBMemberVariableAssigner.OnAssignCCBMemberVariable(target, memberVarAssignmentName, node);
                    }

                    if (!assigned && mCCBMemberVariableAssigner != null)
                    {
                        mCCBMemberVariableAssigner.OnAssignCCBMemberVariable(target, memberVarAssignmentName, node);
                    }
                }
            }
#endif
            // CCB_ENABLE_JAVASCRIPT

            mAnimatedProps.Clear();

            /* Read and add children. */
            int numChildren = ReadInt(false);
            for (int i = 0; i < numChildren; i++)
            {
                CCNode child = ReadNodeGraph(node);
                node.AddChild(child);
            }

            // Call onNodeLoaded
            var nodeAsCCNodeLoaderListener = node as CCNodeLoaderListener;
            if (nodeAsCCNodeLoaderListener != null)
            {
                nodeAsCCNodeLoaderListener.OnNodeLoaded(node, ccNodeLoader);
            }
            else if (mCCNodeLoaderListener != null)
            {
                mCCNodeLoaderListener.OnNodeLoaded(node, ccNodeLoader);
            }

            return node;
        }

        private bool GetBit()
        {
            bool bit;
            byte b = mBytes[mCurrentByte];
            if ((b & (1 << mCurrentBit)) != 0)
            {
                bit = true;
            }
            else
            {
                bit = false;
            }

            mCurrentBit++;

            if (mCurrentBit >= 8)
            {
                mCurrentBit = 0;
                mCurrentByte++;
            }

            return bit;
        }


        private void AlignBits()
        {
            if (mCurrentBit != 0)
            {
                mCurrentBit = 0;
                mCurrentByte++;
            }
        }

        /*
        private string readUTF8()
        {
            
        }
        */
    }
}